/**
 * This file is part of Sonedyan.
 * 
 * Sonedyan is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation;
 * either version 3 of the License, or (at your option) any
 * later version.
 *
 * Sonedyan is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public
 * License along with Octave; see the file COPYING.  If not
 * see <http://www.gnu.org/licenses/>.
 * 
 * Copyright (C) 2009-2012 Jimmy Dubuisson <jimmy.dubuisson@gmail.com>
 */

package org.unige.mpej.eckmann.sonedyan.graph;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.unige.mpej.eckmann.sonedyan.bean.Edge;
import org.unige.mpej.eckmann.sonedyan.bean.Vertex;
import org.unige.mpej.eckmann.sonedyan.utils.DataUtils;
import org.unige.mpej.eckmann.sonedyan.utils.GraphUtils;
import org.unige.mpej.eckmann.sonedyan.utils.MapUtils;

import edu.uci.ics.jung.algorithms.metrics.Metrics;
import edu.uci.ics.jung.algorithms.scoring.BarycenterScorer;
import edu.uci.ics.jung.algorithms.scoring.BetweennessCentrality;
import edu.uci.ics.jung.algorithms.scoring.ClosenessCentrality;
import edu.uci.ics.jung.algorithms.scoring.HITS;
import edu.uci.ics.jung.algorithms.scoring.PageRank;
import edu.uci.ics.jung.algorithms.scoring.VertexScorer;
import edu.uci.ics.jung.algorithms.shortestpath.DijkstraDistance;
import edu.uci.ics.jung.algorithms.shortestpath.DistanceStatistics;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.SparseMultigraph;

import org.apache.log4j.Logger;

/**
 * Set of methods for measuring graph properties
 */
public class GraphMetrics <V extends Vertex, E extends Edge>
{
	private static Logger log = Logger.getLogger(org.unige.mpej.eckmann.sonedyan.graph.GraphMetrics.class);
	
	// hashtable of computed vertex scorers
	private Hashtable<Graph<V, E>, VertexScorer<V, Double>> vertexScorers;
	// hashtable of computed HITS scores
	private Hashtable<Graph<V, E>, VertexScorer<V, HITS.Scores>> hitsScorers;
	// hashtable computed vertex scores maps
	private Hashtable<Graph<V, E>, Map<V, Double>> vertexScoresMaps;
	
	// graph metrics
	public static enum GraphMetric { NUMBER_OF_VERTICES, NUMBER_OF_EDGES, DIAMETER, AVG_CLUSTERING_COEFFICIENT,
		AVG_VERTEX_DEGREE, AVG_VERTEX_BETWEENESS_CENTRALITY, AVG_EDGE_BETWEENESS_CENTRALITY, DENSITY,
		AVG_CLOSENESS_CENTRALITY, AVG_BARYCENTER_CENTRALITY, AVG_HITS_AUTHORITY, AVG_HITS_HUB, AVG_PAGERANK }
	
	// node metrics
	public static enum NodeMetric { DEGREE, CURVATURE, VERTEX_BETWEENESS_CENTRALITY, PAGERANK, 
		CLOSENESS_CENTRALITY, BARYCENTER_CENTRALITY, HITS_AUTHORITY, HITS_HUB }

	// PageRank damping factor
	private static final double PAGERANK_DAMPING_FACTOR = 0.85;
	
	/**
	 * class constructor
	 */
	public GraphMetrics()
	{
		this.vertexScorers = new Hashtable<Graph<V, E>, VertexScorer<V, Double>>();
		this.vertexScoresMaps = new Hashtable<Graph<V, E>, Map<V, Double>>();
		this.hitsScorers = new Hashtable<Graph<V, E>, VertexScorer<V, HITS.Scores>>();
	}
	
	/**
	 * get the specified scorer, if available
	 */
	private VertexScorer<V, Double> getVertexScorer(Graph<V, E> graph, Class c)
	{
		try
		{
			VertexScorer<V, Double> scorer = this.vertexScorers.get(graph);
			
			if (scorer != null && scorer.getClass() == c)
			{
				return scorer;
			}
			else
			{
				return null;
			}
		}
		catch(Exception e)
		{
			log.error("Unable to get scorer: " + e.getMessage());
			return null;
		}
	}
	
	/**
	 * get diff of 2 time series
	 */
	public static TreeMap<Date, Double> getTimeSeriesDifference(TreeMap<Date, Double> timeSeries1, TreeMap<Date, Double> timeSeries2)
	{
		try
		{
			TreeMap<Date, Double> timeSeriesDiff = new TreeMap<Date, Double>();
			
			Set<Date> keys = timeSeries1.keySet();
			
			for (Date key : keys)
			{
				double value1 = timeSeries1.get(key);
				double value2 = timeSeries2.get(key);
				
				timeSeriesDiff.put(key, (value1 - value2));
			}
			
			return timeSeriesDiff;
		}
		catch(Exception e)
		{
			log.error("Unable to get time series difference: " + e.getMessage());
			return null;
		}
	}
	
	/**
	 * get product of 2 time series
	 */
	public static TreeMap<Date, Double> getTimeSeriesProduct(TreeMap<Date, Double> timeSeries1, TreeMap<Date, Double> timeSeries2)
	{
		try
		{
			TreeMap<Date, Double> timeSeriesProduct = new TreeMap<Date, Double>();
			
			Set<Date> keys = timeSeries1.keySet();
			
			for (Date key : keys)
			{
				double value1 = timeSeries1.get(key);
				double value2 = timeSeries2.get(key);
				
				timeSeriesProduct.put(key, (value1 * value2));
			}
			
			return timeSeriesProduct;
		}
		catch(Exception e)
		{
			log.error("Unable to get time series product: " + e.getMessage());
			return null;
		}
	}
	
	/**
	 * get average time series
	 */
	public static TreeMap<Date, Double> getAverageTimeSeries(Hashtable<String, TreeMap<Date, Double>> timeSeries)
	{
		try
		{
			TreeMap<Date, Double> avgTimeSeries = new TreeMap<Date, Double>();
			
			Set<String> keys = timeSeries.keySet();
			
			for (String key : keys)
			{
				TreeMap<Date, Double> nodeTimeSeries = timeSeries.get(key);
				
				Set<Date> dates = nodeTimeSeries.keySet();
				
				for (Date date : dates)
				{
					double value = nodeTimeSeries.get(date);

					Double sum = avgTimeSeries.get(date);
					
					if (sum != null)
					{
						sum += value;
					}
					else
					{
						sum = value;
					}
					
					avgTimeSeries.put(date, sum);
				}
			}
			
			Set<Date> dates = avgTimeSeries.keySet();
			
			for (Date date : dates)
			{
				double value = avgTimeSeries.get(date);
				double avgValue = value / keys.size();
				
				avgTimeSeries.put(date, avgValue);
			}
			
			return avgTimeSeries;
		}
		catch(Exception e)
		{
			log.error("Unable to get average time series: " + e.getMessage());
			return null;
		}
	}
	
	/**
	 * get the nodes time series for the specified node metric 
	 */
	public Hashtable<String, TreeMap<Date, Double>> getNodesTimeSeries(SparseMultigraph<V, E> multiGraph, Date startDate, Date endDate, int timeIncrement, long timeLag, NodeMetric type)
	{
		try
		{
			Hashtable<String, TreeMap<Date, Double>> timeSeries = new Hashtable<String, TreeMap<Date, Double>>();
			Collection<V> vertices = multiGraph.getVertices();
			
			// start at time: start date + timeLag
			long startTime = startDate.getTime() + timeLag;
			long endTime = endDate.getTime() - timeLag;
			long time = startTime;
			
			Calendar cal = Calendar.getInstance();
			cal.setTime(new Date(startTime));
			SimpleDateFormat formatter = new SimpleDateFormat(DataUtils.INPUT_DATE_FORMAT);
	
			double totalDensity = 0;
			int counter = 0;
			
			while (time <= endTime)
			{
				Graph<V, E> snapshotGraph = GraphUtils.getMultiGraphSnapshot(multiGraph, time, timeLag);
				
				int vertexCount = snapshotGraph.getVertexCount();
				int edgeCount = snapshotGraph.getEdgeCount();
				double density = 0;
				
				if (edgeCount > 0)
				{
					density = GraphUtils.getDensity(snapshotGraph);
					totalDensity += density;
					counter++;
				}
				
				log.debug("Extracted snapshot (#nodes '" + vertexCount + "', #edges '" + edgeCount + "', density '" + density + "') for date: " + formatter.format(new Date(time)));
				
				for (V v: vertices)
				{
					TreeMap<Date, Double> nodeTimeSeries = timeSeries.get(v.getId());
					
					if (nodeTimeSeries == null){ nodeTimeSeries = new TreeMap<Date, Double>(); }
					
					double value = this.getNodeMetric(snapshotGraph, v.getId(), type);
					
					nodeTimeSeries.put(new Date(time), value);
					timeSeries.put(v.getId(), nodeTimeSeries);
				}
				
				cal.add(timeIncrement, 1);
				time = cal.getTime().getTime();
			}
			
			log.debug("Average snapshots density: " + (totalDensity / counter));
			
			return timeSeries;
		}
		catch(Exception e)
		{
			log.error("Unable to get nodes time series: " + e.getMessage());
			return null;
		}
	}
	
	/**
	 * get the subgraph nodes time series for the specified node metric 
	 */
	public Hashtable<String, TreeMap<Date, Double>> getSubGraphNodesTimeSeries(SparseMultigraph<V, E> multiGraph, Set<V> subGraphVertices, Date startDate, Date endDate, int timeIncrement, int incrementMultiplicity, long timeLag, NodeMetric type)
	{
		try
		{
			Hashtable<String, TreeMap<Date, Double>> timeSeries = new Hashtable<String, TreeMap<Date, Double>>();
			
			// start at time: start date + timeLag
			long startTime = startDate.getTime() + timeLag;
			long endTime = endDate.getTime() - timeLag;
			long time = startTime;
			
			Calendar cal = Calendar.getInstance();
			cal.setTime(new Date(startTime));
			SimpleDateFormat formatter = new SimpleDateFormat(DataUtils.INPUT_DATE_FORMAT);
			
			log.debug("# sub graph vertices: " + subGraphVertices.size());
			
			double totalDensity = 0;
			int counter = 0;
			
			while (time <= endTime)
			{
				Graph<V, E> snapshotGraph = GraphUtils.getMultiGraphSnapshot(multiGraph, time, timeLag);
				
				log.debug("Extracted snapshot (#nodes '" + snapshotGraph.getVertexCount() + "', #edges '" + snapshotGraph.getEdgeCount() + "') for date: " + formatter.format(new Date(time)));
				
				Graph<V, E> snapshotSubGraph = GraphUtils.getSubGraph2(snapshotGraph, subGraphVertices);
				
				int vertexCount = snapshotSubGraph.getVertexCount();
				int edgeCount = snapshotSubGraph.getEdgeCount();
				double density = 0;
				
				if (edgeCount > 0)
				{
					density = GraphUtils.getDensity(snapshotSubGraph);
					totalDensity += density;
					counter++;
				}
				
				log.debug("Extracted snapshot subgraph (#nodes '" + vertexCount + "', #edges '" + edgeCount + "', density '" + density + "')");
				log.debug("---------");
				
				for (V v: subGraphVertices)
				{
					TreeMap<Date, Double> nodeTimeSeries = timeSeries.get(v.getId());
					
					if (nodeTimeSeries == null){ nodeTimeSeries = new TreeMap<Date, Double>(); }
					
					double value = this.getNodeMetric(snapshotSubGraph, v.getId(), type);
					
					nodeTimeSeries.put(new Date(time), value);
					timeSeries.put(v.getId(), nodeTimeSeries);
				}
				
				cal.add(timeIncrement, incrementMultiplicity);
				time = cal.getTime().getTime();
			}
			
			log.debug("Average snapshots subgraphs density: " + (totalDensity / counter));
			
			return timeSeries;
		}
		catch(Exception e)
		{
			log.error("Unable to get nodes time series: " + e.getMessage());
			return null;
		}
	}
	
	/**
	 * get a sorted map (metric value -> node id) a the graph nodes metric values
	 */
	public Map<String, Double> getSortedNodeMetric(Graph<V, E> graph, NodeMetric type)
	{
		Map<String, Double> map = new HashMap<String, Double>();
		
		Collection<V> vertices = graph.getVertices();
		
		for (V v: vertices)
		{
			double value = getNodeMetric(graph, v, type);
			
			map.put(v.getId(), value);
		}
		
		return MapUtils.sortByValue(map, false);
	}
	
	/**
	 * get the specified node degree
	 */
	public int getNodeDegree(Graph<V, E> graph, String id)
	{
		Collection<V> vertices = graph.getVertices();
		
		for (V v: vertices)
		{
			if (v.getId().equals(id))
			{
				return graph.degree(v);
			}
		}
		
		return -1;
	}
	
	/**
	 * get degree -> #nodes distribution of the specified graph
	 */
	public Map<Integer, Integer> getDegreeDistribution(Graph<V, E> graph)
	{
		// degree -> number of nodes hash map
		HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
		
		Collection<V> vertices = graph.getVertices();
		
		for (V a: vertices)
		{
			int degree = graph.degree(a);
			
			if (hashMap.containsKey(degree))
			{
				int oldCount = hashMap.get(degree);
				
				hashMap.put(degree, oldCount + 1);
			}
			else
			{
				hashMap.put(degree, 1);
			}
		}		
		
		return hashMap;
	}
	
	/**
	 * get the degree -> curvature distribution
	 */
	public HashMap<Integer, Double> getDegreeCurvatureDistribution(Graph<V, E> graph)
	{
		Map<V, Double> map = Metrics.clusteringCoefficients(graph);
		
		HashMap<Integer, Integer> degreeHashMap = new HashMap<Integer, Integer>();
		HashMap<Integer, Double> curvatureHashMap = new HashMap<Integer, Double>();
		
		Collection<V> vertices = map.keySet();
		
		for (V a: vertices)
		{
			int degree = graph.degree(a);
			
			if (degreeHashMap.containsKey(degree))
			{
				int oldCount = degreeHashMap.get(degree);
				double oldValue = curvatureHashMap.get(degree);
				
				degreeHashMap.put(degree, oldCount + 1);
				curvatureHashMap.put(degree, oldValue + map.get(a));
			}
			else
			{
				degreeHashMap.put(degree, 1);
				curvatureHashMap.put(degree, map.get(a));
			}
		}	
		
		// compute the average
		Collection<Integer> degrees = curvatureHashMap.keySet();
		
		for (int degree: degrees)
		{
			double oldValue = curvatureHashMap.get(degree);
			double newValue = oldValue / degreeHashMap.get(degree);
			curvatureHashMap.put(degree, newValue);
		}
		
		return curvatureHashMap;
	}
	
	/**
	 * get the average metric value
	 * 
	 * Graph Metric Types:
	 * 
	 * averageClusteringCoefficient
	 * averageVertexDegree
	 * averageVertexBetweennessCentrality
	 * averageEdgeBetweennessCentrality
	 * averageClosenessCentrality
	 * averageBarycenterCentrality
	 * averageHitsAuthority
	 * averageHitsHub
	 * averagePagerank
	 */
	public double getAverageGraphMetricValue(Graph<V, E> graph, GraphMetric type)
	{
		return this.getAverageGraphMetricValueOfNodes(graph, type, null);
	}
	
	/**
	 * get average node degree of the specified nodes
	 * 
	 * Graph Metric Types:
	 * 
	 * averageClusteringCoefficient
	 * averageVertexDegree
	 * averageVertexBetweennessCentrality
	 * averageEdgeBetweennessCentrality
	 * averageClosenessCentrality
	 * averageBarycenterCentrality
	 * averageHitsAuthority
	 * averageHitsHub
	 * averagePagerank
	 */
	public double getAverageGraphMetricValueOfNodes(Graph<V, E> graph, GraphMetric type, Set<String> ids)
	{
		try
		{
			Collection<V> vertices = graph.getVertices();
			
			double value = 0;
			int count = 0;
			
			BetweennessCentrality<V, E> centrality;
			
			switch (type)
			{
				case AVG_CLUSTERING_COEFFICIENT:
					Map<V, Double> map = this.vertexScoresMaps.get(graph);
					
					if (map == null)
					{
						map = Metrics.clusteringCoefficients(graph);
						this.vertexScoresMaps.put(graph, map);
					}
					
					Set<V> keySet = map.keySet();
					
					for (V v : keySet)
					{
						if (ids == null || ids.contains(v.getId()))
						{
							value += map.get(v);
							count++;
						}
					}
					
					return value / count;
				
				case AVG_VERTEX_DEGREE:
					vertices = graph.getVertices();
					
					for (V v : vertices)
					{
						if (ids == null || ids.contains(v.getId()))
						{
							value += graph.degree(v);
							count++;
						}
					}
					
					return value / count;
					
				case AVG_VERTEX_BETWEENESS_CENTRALITY:
					centrality = (BetweennessCentrality<V, E>) this.getVertexScorer(graph, BetweennessCentrality.class);
					
					if (centrality == null)
					{
						centrality = new BetweennessCentrality<V, E>(graph);
						this.vertexScorers.put(graph, centrality);
					}
					
					vertices = graph.getVertices();
					
					for (V v : vertices)
					{
						if (ids == null || ids.contains(v.getId()))
						{
							value += centrality.getVertexScore(v);
							count++;
						}
					}
					
					return value / count;
					
				case AVG_EDGE_BETWEENESS_CENTRALITY:
					centrality = (BetweennessCentrality<V, E>) this.getVertexScorer(graph, BetweennessCentrality.class);
					
					if (centrality == null)
					{
						centrality = new BetweennessCentrality<V, E>(graph);
						this.vertexScorers.put(graph, centrality);
					}
					
					Collection<E> edges = graph.getEdges();
					
					for (E e : edges)
					{
						if (ids == null || (ids.contains(e.getFromActorId()) && ids.contains(e.getToActorId())))
						{
							value += centrality.getEdgeScore(e);
							count++;
						}
					}
					
					return value / count;
					
				case AVG_CLOSENESS_CENTRALITY:
					ClosenessCentrality<V, E> closenessCentrality = (ClosenessCentrality<V, E>) this.getVertexScorer(graph, ClosenessCentrality.class);
					
					if (closenessCentrality == null)
					{
						closenessCentrality = new ClosenessCentrality<V, E>(graph);
						this.vertexScorers.put(graph, closenessCentrality);
					}
					
					vertices = graph.getVertices();
					
					for (V v : vertices)
					{
						if (ids == null || ids.contains(v.getId()))
						{
							value += closenessCentrality.getVertexScore(v);
							count++;
						}
					}
					
					return value / count;
					
					
				case AVG_BARYCENTER_CENTRALITY:
					BarycenterScorer<V, E> barycenterScorer = (BarycenterScorer<V, E>) this.getVertexScorer(graph, BarycenterScorer.class);
					
					if (barycenterScorer == null)
					{
						barycenterScorer = new BarycenterScorer<V, E>(graph);
						this.vertexScorers.put(graph, barycenterScorer);
					}
					
					vertices = graph.getVertices();
					
					for (V v : vertices)
					{
						if (ids == null || ids.contains(v.getId()))
						{
							value += barycenterScorer.getVertexScore(v);
							count++;
						}
					}
					
					return value / count;
					
				case AVG_PAGERANK:
					PageRank<V, E> pageRank = (PageRank<V, E>) this.getVertexScorer(graph, PageRank.class);
					
					if (pageRank == null)
					{
						pageRank = new PageRank<V, E>(graph, (1 - PAGERANK_DAMPING_FACTOR));
						this.vertexScorers.put(graph, pageRank);
					}
					
					vertices = graph.getVertices();
					
					for (V v : vertices)
					{
						if (ids == null || ids.contains(v.getId()))
						{
							value += pageRank.getVertexScore(v);
							count++;
						}
					}
					
					return value / count;
					
				case AVG_HITS_AUTHORITY:
					HITS<V, E> hits = (HITS<V, E>) this.hitsScorers.get(graph);
					
					if (hits == null)
					{
						hits = new HITS<V, E>(graph);
						this.hitsScorers.put(graph, hits);
					}
					
					vertices = graph.getVertices();
					
					for (V v : vertices)
					{
						if (ids == null || ids.contains(v.getId()))
						{
							value += hits.getVertexScore(v).authority;
							count++;
						}
					}
					
					return value / count;
					
					
				case AVG_HITS_HUB:
					HITS<V, E> hits2 = (HITS<V, E>) this.hitsScorers.get(graph);
					
					if (hits2 == null)
					{
						hits2 = new HITS<V, E>(graph);
						this.hitsScorers.put(graph, hits2);
					}
					
					vertices = graph.getVertices();
					
					for (V v : vertices)
					{
						if (ids == null || ids.contains(v.getId()))
						{
							value += hits2.getVertexScore(v).hub;
							count++;
						}
					}
					
					return value / count;
					
				default:
					throw new Exception("The specified vertex metric is not supported");
			}
		}
		catch(Exception e)
		{
			log.error("Unable to get average metric: " + e.getMessage());
			return 0;
		}
	}
	
	/**
	 * get the specified graph metric
	 * 
	 * Graph Metric Types:
	 * 
	 * #vertices: Number of vertices
	 * #nodes: Number of nodes
	 * diameter: diameter of the graph (maximum shortest path length over all pairs of connected vertices)
	 * averageClusteringCoefficient
	 * averageVertexDegree
	 * averageVertexBetweennessCentrality
	 * averageEdgeBetweennessCentrality
	 */
	public double getGraphMetric(Graph<V, E> graph, GraphMetric type)
	{
		switch(type)
		{
			case NUMBER_OF_VERTICES:
				return (double) graph.getVertexCount();
				
			case NUMBER_OF_EDGES:
				return (double) graph.getEdgeCount();
				
			case DIAMETER:
				return (double) DistanceStatistics.diameter(graph, new DijkstraDistance<V, E>(graph) , true);
				
			case AVG_CLUSTERING_COEFFICIENT:
				return getAverageGraphMetricValue(graph, type);
				
			case AVG_VERTEX_DEGREE:
				return getAverageGraphMetricValue(graph, type);
				
			case AVG_VERTEX_BETWEENESS_CENTRALITY:
				return getAverageGraphMetricValue(graph, type);
				
			case AVG_EDGE_BETWEENESS_CENTRALITY:
				return getAverageGraphMetricValue(graph, type);
			
			case DENSITY:
				int numberOfVertices = graph.getVertexCount();
				int numberOfEdges = graph.getEdgeCount();
				
				double numerator = 2 * numberOfEdges;
				double quotient = numberOfVertices * (numberOfVertices - 1);
				
				double density = numerator / quotient;
				log.debug("#E: " + numberOfEdges + ", #V: " + numberOfVertices + " Density: " + density);
				return density;
				
			default:
				return 0;
		}	
	}
	
	/**
	 * get the specified node metric
	 */
	public double getNodeMetric(Graph<V, E> graph, V actor, NodeMetric type)
	{
		try
		{
			if (actor == null)
			{
				throw new Exception("Specified actor is null!");
			}
			
			switch(type)
			{
				case DEGREE:
					return graph.degree(actor);

				case CURVATURE:
					Map<V, Double> map = this.vertexScoresMaps.get(graph);
					
					if (map == null)
					{
						map = Metrics.clusteringCoefficients(graph);
						this.vertexScoresMaps.put(graph, map);
					}
					
					return map.get(actor);
						
				case VERTEX_BETWEENESS_CENTRALITY:
					BetweennessCentrality<V, E> centrality = (BetweennessCentrality<V, E>) this.getVertexScorer(graph, BetweennessCentrality.class);
					
					if (centrality == null)
					{
						centrality = new BetweennessCentrality<V, E>(graph);
						this.vertexScorers.put(graph, centrality);
					}
					
					return centrality.getVertexScore(actor);
					
				case PAGERANK:
					PageRank<V, E> pageRank = (PageRank<V, E>) this.getVertexScorer(graph, PageRank.class);
					
					if (pageRank == null)
					{
						pageRank = new PageRank<V, E>(graph, (1 - PAGERANK_DAMPING_FACTOR));
						this.vertexScorers.put(graph, pageRank);
					}
					
					return pageRank.getVertexScore(actor);
					
				case CLOSENESS_CENTRALITY:
					ClosenessCentrality<V, E> closenessCentrality = (ClosenessCentrality<V, E>) this.getVertexScorer(graph, ClosenessCentrality.class);
					
					if (closenessCentrality == null)
					{
						closenessCentrality = new ClosenessCentrality<V, E>(graph);
						this.vertexScorers.put(graph, closenessCentrality);
					}
					
					return closenessCentrality.getVertexScore(actor);
					
				case BARYCENTER_CENTRALITY:
					BarycenterScorer<V, E> barycenterScorer = (BarycenterScorer<V, E>) this.getVertexScorer(graph, BarycenterScorer.class);
					
					if (barycenterScorer == null)
					{
						barycenterScorer = new BarycenterScorer<V, E>(graph);
						this.vertexScorers.put(graph, barycenterScorer);
					}
					
					return barycenterScorer.getVertexScore(actor);
					
				case HITS_AUTHORITY:
					HITS<V, E> hits = (HITS<V, E>) this.hitsScorers.get(graph);
					
					if (hits == null)
					{
						hits = new HITS<V, E>(graph);
						this.hitsScorers.put(graph, hits);
					}
					
					return hits.getVertexScore(actor).authority;
				
				case HITS_HUB:
					HITS<V, E> hits2 = (HITS<V, E>) this.hitsScorers.get(graph);
					
					if (hits2 == null)
					{
						hits2 = new HITS<V, E>(graph);
						this.hitsScorers.put(graph, hits2);
					}
					
					return hits2.getVertexScore(actor).hub;
				
				default:
					throw new Exception("The specified vertex metric is not supported");
			}
		}
		catch(Exception e)
		{
			log.error("Unable to get node metric: " + e.getMessage());
			return 0;
		}
	}
	
	/**
	 * get the specified node metric
	 */
	public double getNodeMetric(Graph<V, E> graph, String nodeId, NodeMetric type)
	{
		try
		{
			V actor = GraphUtils.getActorById(graph, nodeId);
			
			if (actor == null)
			{
				throw new Exception("Unable to get specified actor!");
			}
			else
			{
				return this.getNodeMetric(graph, actor, type);
			}
		}
		catch(Exception e)
		{
			log.error("Unable to get node metric: " + e.getMessage());
			return 0;
		}
	}
	
}
